Skip to content
This repository was archived by the owner on Jun 24, 2026. It is now read-only.

feat: Chrome Sidecar MCP環境の実装#241

Open
windschord wants to merge 11 commits into
mainfrom
feat/chrome-sidecar-mcp
Open

feat: Chrome Sidecar MCP環境の実装#241
windschord wants to merge 11 commits into
mainfrom
feat/chrome-sidecar-mcp

Conversation

@windschord

@windschord windschord commented Mar 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Docker環境のセッション起動時にセッション専用のChromeサイドカーコンテナを自動起動し、Chrome DevTools MCP経由でClaude Codeからブラウザ操作を可能にする機能を実装
  • 環境設定画面でChrome Sidecarの有効/無効・イメージ設定が可能
  • セッション詳細画面にChromeデバッグポート情報を表示

Changes

Backend

  • ChromeSidecarService (src/services/chrome-sidecar-service.ts): 新規サービス。Chromeコンテナのライフサイクル管理(起動・CDPヘルスチェック・停止・孤立リソースクリーンアップ)
  • DockerAdapter拡張 (src/services/adapters/docker-adapter.ts): createSession/destroySessionにサイドカーフェーズを統合。.mcp.jsonへの--browserUrl注入
  • DBスキーマ (src/db/schema.ts): Sessionテーブルにchrome_container_id/chrome_debug_portカラム追加
  • バリデーション (src/lib/chrome-sidecar-validator.ts): chromeSidecar config検証(latestタグ禁止、イメージ名形式チェック)
  • サーバー起動時クリーンアップ (server.ts): 孤立Chromeコンテナ/ネットワークの自動検出・削除

Frontend

  • ChromeSidecarSection (src/components/environments/): 環境設定フォームのChrome Sidecarトグル/イメージ/タグ設定
  • ChromeDebugInfo (src/components/sessions/): セッション詳細のデバッグポート表示
  • ChromeBadge (src/components/sessions/): セッションリストのChromeバッジ

Security

  • CapDrop ALL, no-new-privileges
  • CDPポート(9222)は127.0.0.1のみにバインド
  • セッション専用Dockerブリッジネットワークで隔離
  • Chromeコンテナメモリ制限512MB

Design Documents

  • 要件定義: docs/sdd/requirements/chrome-sidecar/ (既存)
  • 技術設計書: docs/sdd/design/chrome-sidecar/ (新規10ファイル)
  • タスク計画: docs/sdd/tasks/chrome-sidecar/ (新規10ファイル)

Test plan

  • 全3302テストパス(新規66テスト含む)
  • ChromeSidecarService: 正常系/異常系/クリーンアップ(14テストケース)
  • DockerAdapter拡張: サイドカー有効/無効/タイムアウト/障害(11テストケース)
  • Environment APIバリデーション: chromeSidecar config検証(9テストケース)
  • Session APIレスポンス: chromeフィールド含有確認
  • UIコンポーネント: フォーム表示/バリデーション/バッジ表示
  • DBスキーマ: カラム追加・NULL許可・既存データ互換性
  • 型定義: ChromeSidecarConfig後方互換性

Closes #226

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新機能
    • Docker環境でChromeサイドカーを有効化可能に(イメージ/タグ指定、セッション画面でデバッグ表示と「Chrome」バッジ、ブラウザURL注入)
  • API
    • 環境APIにサイドカー設定の入力検証を追加(型・イメージ・タグ検査、HOST環境では無視)
    • セッションAPIのレスポンスにサイドカー関連フィールドを追加(存在しない場合はnull)
  • Database
    • Sessionテーブルにchrome_container_id / chrome_debug_port列を追加(NULL許可)
  • ドキュメント & テスト
    • 設計ドキュメント、単体/統合テスト、起動時のオーファン資源クリーンアップ仕様を追加

Docker環境のセッション起動時にセッション専用のChromeサイドカーコンテナを
自動起動し、Chrome DevTools MCP経由でClaude Codeからブラウザ操作を可能にする。

## 主な変更
- ChromeSidecarService: コンテナライフサイクル管理(起動/停止/クリーンアップ)
- DockerAdapter拡張: サイドカー起動→.mcp.json注入→ネットワーク接続フロー
- DBスキーマ: Sessionテーブルにchrome_container_id/chrome_debug_portカラム追加
- Environment APIバリデーション: chromeSidecar config検証(latestタグ禁止等)
- 環境設定UI: Chrome Sidecarトグル/イメージ設定
- セッションUI: デバッグポート表示/Chromeバッジ
- サーバー起動時: 孤立Chromeコンテナ/ネットワークの自動クリーンアップ

## セキュリティ
- CapDrop ALL, no-new-privileges
- CDPポートは127.0.0.1のみにバインド
- セッション専用ブリッジネットワークで隔離
- コンテナメモリ制限512MB

Closes #226

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 21, 2026 10:30
@coderabbitai

coderabbitai Bot commented Mar 21, 2026

Copy link
Copy Markdown

Walkthrough

Dockerセッション起動時にオプションのChromeサイドカーを起動・管理する機能を追加。環境設定/UI/バリデーション、DBマイグレーション、ChromeSidecarService(起動/停止/接続/クリーンアップ)、DockerAdapter統合、サーバ起動時クリーンアップ、および関連テスト群を導入した。

Changes

Cohort / File(s) Summary
設計ドキュメント
docs/sdd/design/chrome-sidecar/index.md, docs/sdd/design/chrome-sidecar/api/environment-api.md, docs/sdd/design/chrome-sidecar/api/session-api.md, docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md, docs/sdd/design/chrome-sidecar/components/db-schema.md, docs/sdd/design/chrome-sidecar/components/docker-adapter.md, docs/sdd/design/chrome-sidecar/components/environment-ui.md, docs/sdd/design/chrome-sidecar/sequences/*
Chrome Sidecar の要件・設計・シーケンス・サービス契約・DB設計・UI仕様を追加。
DB スキーマ & マイグレーション
drizzle/0001_youthful_naoko.sql, drizzle/meta/0001_snapshot.json, drizzle/meta/_journal.json, src/db/schema.ts
sessions テーブルへ chrome_container_id (TEXT) と chrome_debug_port (INTEGER) を追加し、Drizzle スナップショット/ジャーナルを更新。
型定義
src/types/environment.ts
ChromeSidecarConfig を追加し、DockerEnvironmentConfigchromeSidecar?: ChromeSidecarConfig を導入。
バリデーション関数 & テスト
src/lib/chrome-sidecar-validator.ts, src/app/api/environments/__tests__/chrome-sidecar-validation.test.ts
Environment API 用の chromeSidecar 検証関数を追加(enabledはboolean、image/tagパターン、tagにlatest禁止)と単体テスト。
API 層変更
src/app/api/projects/[project_id]/environment/route.ts
PUT 環境更新で DOCKER タイプに対し validateChromeSidecarConfig を適用。HOST タイプは無視。検証失敗で 400 応答。
ChromeSidecar サービス実装 & テスト
src/services/chrome-sidecar-service.ts, src/services/__tests__/chrome-sidecar-service.test.ts, src/services/__tests__/chrome-sidecar-cleanup-integration.test.ts
ChromeSidecarService を追加(start/stop/connect/cleanup/getActiveSidecarCount 等)、CDPヘルスチェック、ラベル/命名規約、孤立リソースクリーンアップを実装しテストを追加。
DockerAdapter 統合 & テスト
src/services/adapters/docker-adapter.ts, src/services/adapters/__tests__/docker-adapter-chrome-sidecar.test.ts, src/services/environment-adapter.ts, src/services/pty-session-manager.ts
createSession/destroySession にサイドカー起動・停止・ネットワーク接続・DB更新・ブラウザURL注入(injectBrowserUrl)を組込。失敗はログ記録でフォールバック。
フロントエンド UI & テスト
src/components/environments/ChromeSidecarSection.tsx, src/components/projects/ProjectEnvironmentSection.tsx, src/components/environments/__tests__/chrome-sidecar-form.test.tsx
環境編集画面へ Chrome Sidecar セクション(トグル、image/tag、latest 禁止、送信挙動)を追加し対応テストを追加。
セッション UI 表示 & テスト
src/components/sessions/ChromeDebugInfo.tsx, src/components/sessions/ChromeBadge.tsx, src/components/__tests__/session-chrome-info.test.tsx
セッション詳細でデバッグポート表示/デバッグURL、セッション一覧に「Chrome」バッジを追加しテストを追加。
DB スキーマ検証テスト
src/db/__tests__/schema-chrome-sidecar.test.ts
追加カラムの存在・型(SQLiteText/SQLiteInteger)および NULL 許容を検証するテストを追加。
サーバー起動フロー
server.ts
サーバ起動時に ChromeSidecarService.cleanupOrphaned() を呼び、孤立コンテナ/ネットワークを回収(失敗はログに記録して起動継続)。
マイグレーション CI スクリプト
scripts/test-migration-ci.ts
CI 用テストマイグレーションの適用手順を調整(初期マイグレーションの直接適用等)およびシナリオ準備を更新。
テスト群(追加)
src/services/__tests__/chrome-sidecar-cleanup-integration.test.ts, src/services/adapters/__tests__/docker-adapter-chrome-sidecar.test.ts, src/services/__tests__/chrome-sidecar-service.test.ts, 他多数
サービス・アダプタ・API・UI の新機能を網羅するユニット/統合テストを多数追加。

Sequence Diagram(s)

sequenceDiagram
    participant User as PTYSessionManager
    participant DA as DockerAdapter
    participant CS as ChromeSidecarService
    participant Docker as Docker Daemon
    participant DB as Database
    participant CDP as Chrome Container

    User->>DA: createSession(sessionId, options)
    alt chromeSidecar.enabled
        DA->>CS: startSidecar(sessionId, config)
        CS->>Docker: create network cw-net-{sessionId}
        Docker-->>CS: network created
        CS->>Docker: create & start Chrome container
        Docker-->>CS: container started
        CS->>Docker: inspect container → debugPort
        CS->>CDP: GET 127.0.0.1:<debugPort>/json/version (poll, 30s)
        alt Health check success
            CS-->>DA: SidecarStartResult(success:true, browserUrl)
        else Health check fail
            CS->>Docker: stop & remove container
            CS->>Docker: remove network
            CS-->>DA: SidecarStartResult(success:false, error)
        end
    else sidecar disabled
        DA-->>User: continue without sidecar
    end

    DA->>Docker: create & start Claude container
    alt sidecar succeeded
        DA->>Docker: connect Claude container to cw-net-{sessionId}
        DA->>DB: write chrome_container_id, chrome_debug_port
    end
    DA-->>User: session created
Loading
sequenceDiagram
    participant Server as App Server
    participant CS as ChromeSidecarService
    participant Docker as Docker Daemon
    participant DB as Database

    Server->>Server: startup
    Server->>CS: cleanupOrphaned()

    note over CS: Phase 1: DB-driven cleanup
    CS->>DB: SELECT sessions WHERE chrome_container_id IS NOT NULL
    DB-->>CS: sessions list
    loop per session
        CS->>Docker: inspect container
        alt container missing/stopped
            CS->>Docker: remove network cw-net-{sessionId}
            CS->>DB: UPDATE session SET chrome_container_id=NULL, chrome_debug_port=NULL
        else running
            CS-->>CS: skip
        end
    end

    note over CS: Phase 2: Label-driven cleanup
    CS->>Docker: list containers with cw-sidecar label
    Docker-->>CS: container list
    loop orphaned container
        CS->>DB: check session exists
        alt missing
            CS->>Docker: stop container
        end
    end

    CS->>Docker: list networks by label
    Docker-->>CS: network list
    loop each network
        CS->>Docker: list connected containers
        alt zero connected
            CS->>Docker: remove network
        end
    end

    CS-->>Server: cleanup complete
    Server->>Server: continue startup
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

ぼくは小さなウサギエンジニア🐇
ネットに橋をかけてブラウザを呼ぶよ🌉
迷子のサイドカーも見つけて丁寧に片付ける🐾
エラーが出てもログで歌ってサービスは走る⚙️
にんじん片手に今日もぴょん! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PRタイトルは「feat: Chrome Sidecar MCP環境の実装」で、変更の主要部分(Chrome Sidecarの実装)を明確に説明しており、変更内容と完全に合致しています。
Linked Issues check ✅ Passed 全ての要件(US-001~US-004)が実装されています:DBスキーマ拡張[#226]、ChromeSidecarService実装[#226]、DockerAdapter統合[#226]、バリデーション実装[#226]、UI拡張[#226]、APIレスポンス拡張[#226]、サーバー起動時クリーンアップ[#226]。
Out of Scope Changes check ✅ Passed 全変更はChrome Sidecar実装に関連しており、スコープ外の変更はありません。DBマイグレーション、テスト、ドキュメント、コンポーネントすべてが#226の要件に対応しています。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chrome-sidecar-mcp

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown

⚠️ Textlint検査で問題が検出されました。詳細はActionsログを確認してください。

ローカルで修正するには:

pnpm install
pnpm run textlint
pnpm run textlint:fix  # 自動修正を試みる

This comment was marked as outdated.

- textlint: 対応要件リストのカンマ超過を箇条書きに変更
- test-migration: シナリオ2を初期マイグレーションのみ直接適用に変更
  ALTER TABLE ADD COLUMNはCREATE TABLE IF NOT EXISTSと異なり冪等でないため、
  旧DBシミュレーションでは初期スキーマのみ適用しmigrate()で増分マイグレーション実行
  旧セッションの新カラムがNULLであることも検証追加

Refs #226

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (11)
docs/sdd/design/chrome-sidecar/index.md (1)

7-25: アスキー図のコードブロックに言語指定を追加することを検討してください。

markdownlintの警告(MD040)に対応するため、フェンスドコードブロックに textplaintext を指定することで警告を抑制できます。

💡 修正案
-```
+```text
 Session起動フロー (サイドカー有効時):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/sdd/design/chrome-sidecar/index.md` around lines 7 - 25, The fenced
ASCII art block in index.md triggers markdownlint MD040; update the code fence
that wraps the Session起動フロー ASCII diagram to include a plain text language tag
(e.g., use ```text or ```plaintext) so the block is explicitly marked as
non-code and the linter warning is suppressed—locate the ASCII diagram block
(the fenced block containing "Session起動フロー (サイドカー有効時):" and the diagram) and add
the language identifier to the opening fence.
src/app/api/projects/[project_id]/environment/route.ts (1)

216-228: Chrome Sidecarのバリデーションロジックは正しく実装されています。

DOCKER環境の場合のみバリデーションを実行し、HOST環境では無視する動作は要件と整合しています。

ただし、effectiveTypeの計算が171行目と217行目で重複しています。可読性向上のため、config検証ブロックの先頭で一度だけ計算することを検討してください。

♻️ リファクタリング案
     if (config !== undefined) {
       // config は plain object のみ許可
       // ...既存のバリデーション...
       
+      // 実効環境タイプを一度だけ計算
+      const effectiveType = type ?? existing.type;
+
       // skipPermissions のバリデーション
       if (sanitizedConfig.skipPermissions !== undefined) {
-        const effectiveType = type ?? existing.type;
         if (effectiveType !== 'DOCKER') {
           // ...
         }
       }
       
       // ...portMappings, volumeMounts...
       
       // chromeSidecar のバリデーション(DOCKER環境の場合のみ)
-      const effectiveType = type ?? existing.type;
       if (effectiveType === 'DOCKER') {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/projects/`[project_id]/environment/route.ts around lines 216 -
228, The code duplicates computation of effectiveType (using type ??
existing.type) in multiple places; compute effectiveType once at the start of
the config validation block and reuse it for all subsequent checks (including
the DOCKER-only chromeSidecar call), then remove the second calculation to
improve readability—update references in the chromeSidecar validation to use the
single effectiveType and ensure validateChromeSidecarConfig(sanitizedConfig)
remains unchanged.
docs/sdd/design/chrome-sidecar/sequences/session-teardown.md (1)

13-68: シーケンス図のコードブロックに言語指定を追加することを検討してください。

markdownlintの警告(MD040)に対応するため、フェンスドコードブロックに text を指定することで警告を抑制できます。

💡 修正案
-```
+```text
 Client          PTYSessionMgr     DockerAdapter     ChromeSidecarSvc    DockerClient       DB
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/sdd/design/chrome-sidecar/sequences/session-teardown.md` around lines 13
- 68, Add a language specifier "text" to the fenced sequence diagram code block
(the triple-backtick block that starts with the diagram header line "Client     
PTYSessionMgr     DockerAdapter ...") to silence markdownlint MD040; update the
opening fence from ``` to ```text (and apply the same change to any other
similar fenced-diagram blocks in session-teardown.md) so the diagram remains
unchanged but is explicitly marked as plain text.
docs/sdd/design/chrome-sidecar/api/environment-api.md (1)

45-45: Docker imageの正規表現がレジストリポート指定をサポートしていない可能性

45行目の正規表現 /^[-a-z0-9._/]+$/ にはコロン(:)が含まれていません。プライベートレジストリでポート指定が必要な場合(例: registry:5000/image)、バリデーションが失敗します。

68行目のエラーメッセージでは lowercase alphanumeric, dots, slashes, hyphens と記載されており、コロンは意図的に除外されているように見えますが、実運用でプライベートレジストリを使用する場合は考慮が必要です。

コロンを許可する場合の修正案
- if (typeof sidecar.image !== 'string' || !/^[-a-z0-9._/]+$/.test(sidecar.image)) {
-   return 'chromeSidecar.image must be a valid Docker image name (lowercase alphanumeric, dots, slashes, hyphens)';
+ if (typeof sidecar.image !== 'string' || !/^[-a-z0-9._/:]+$/.test(sidecar.image)) {
+   return 'chromeSidecar.image must be a valid Docker image name (lowercase alphanumeric, dots, slashes, hyphens, colons)';

Also applies to: 67-68

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/sdd/design/chrome-sidecar/api/environment-api.md` at line 45, The
validation regex for chromeSidecar.image currently listed as `/^[-a-z0-9._/]+$/`
rejects colon characters used for registry ports/tags; update the pattern
referenced for chromeSidecar.image to allow colons (add ':' to the allowed
character class) and update the associated error message (the string describing
allowed characters, e.g., "lowercase alphanumeric, dots, slashes, hyphens") to
mention colons (or "ports/tags") so the docs and message reflect the new
acceptance of colon-separated registry:port formats.
src/components/environments/__tests__/chrome-sidecar-form.test.tsx (1)

1-72: テストカバレッジが良好です。

主要な機能(有効/無効状態、デフォルト値表示、latestタグバリデーション、トグルコールバック)が網羅されています。

オプションとして、onImageChangeonTagChangeのコールバックテストを追加すると、より完全なカバレッジになります。

追加テストケースの例
it('imageフィールドの変更でonImageChangeが呼ばれること', () => {
  const onImageChange = vi.fn();
  render(<ChromeSidecarSection {...defaultProps} enabled={true} onImageChange={onImageChange} />);
  const imageInput = screen.getByLabelText('Chrome Image');
  fireEvent.change(imageInput, { target: { value: 'new-image' } });
  expect(onImageChange).toHaveBeenCalledWith('new-image');
});

it('tagフィールドの変更でonTagChangeが呼ばれること', () => {
  const onTagChange = vi.fn();
  render(<ChromeSidecarSection {...defaultProps} enabled={true} onTagChange={onTagChange} />);
  const tagInput = screen.getByLabelText('Chrome Tag');
  fireEvent.change(tagInput, { target: { value: '132.0.0.0' } });
  expect(onTagChange).toHaveBeenCalledWith('132.0.0.0');
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/environments/__tests__/chrome-sidecar-form.test.tsx` around
lines 1 - 72, Add two unit tests in the existing chrome-sidecar-form.test.tsx to
assert that ChromeSidecarSection calls the provided callbacks when inputs
change: render ChromeSidecarSection with enabled={true} and a mock
onImageChange, simulate changing the "Chrome Image" input (fireEvent.change) and
expect onImageChange toHaveBeenCalledWith('new-image'); similarly render with a
mock onTagChange, change the "Chrome Tag" input and expect onTagChange
toHaveBeenCalledWith('132.0.0.0'); use the existing defaultProps and
screen.getByLabelText to locate inputs and vi.fn() for mocks.
src/app/api/environments/__tests__/chrome-sidecar-validation.test.ts (1)

6-103: chromeSidecar の不正型ケースを追加で固定化してください

chromeSidecar: false / 0 / '' / [] のような「キーはあるがオブジェクトでない」入力のテストがないため、境界バグの回帰を防げません。

追加テスト例
+    it('chromeSidecar がオブジェクトでない場合: エラー', () => {
+      const cases = [
+        { chromeSidecar: false },
+        { chromeSidecar: 0 },
+        { chromeSidecar: '' },
+        { chromeSidecar: [] },
+      ] as Array<Record<string, unknown>>;
+
+      for (const config of cases) {
+        const error = validateChromeSidecarConfig(config);
+        expect(error).not.toBeNull();
+      }
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/environments/__tests__/chrome-sidecar-validation.test.ts` around
lines 6 - 103, Add tests to cover cases where chromeSidecar key exists but is
not an object (e.g., chromeSidecar: false, 0, '', []) by calling
validateChromeSidecarConfig with each and asserting the returned error is not
null and mentions "chromeSidecar" (or "chromeSidecar.enabled" depending on
current error text); locate the test block using the existing describe for
chrome-sidecar validation and the validateChromeSidecarConfig calls to add these
new it(...) cases to prevent regressions for non-object types.
src/components/sessions/ChromeDebugInfo.tsx (1)

28-34: デバッグポート判定は truthy ではなく null 判定に寄せると安全です

現在の判定だと 0 を未設定扱いします。型意図に合わせて chromeDebugPort !== null で判定する方が明確です。

修正案
+  const hasDebugPort = chromeDebugPort !== null;
   return (
@@
-            {chromeDebugPort
+            {hasDebugPort
               ? 'Running'
               : 'Running (debug port unavailable)'}
@@
-        {chromeDebugPort && (
+        {hasDebugPort && (
           <div className="flex items-center gap-2">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/sessions/ChromeDebugInfo.tsx` around lines 28 - 34, The code
currently treats chromeDebugPort as truthy/falsy which will treat 0 as
unavailable; change both checks to explicit null comparisons (use
chromeDebugPort !== null for the ternary that shows 'Running' vs 'Running (debug
port unavailable)' and for the conditional rendering that shows the debug-port
div) so that a port value of 0 is handled according to the type intent and only
null indicates "unavailable."
src/app/api/sessions/__tests__/chrome-sidecar-response.test.ts (1)

120-133: このケースはAPI実装を検証しておらず、回帰検知力がありません

{ ...dbResult } の自己検証になっているため、GET /api/sessions/:id の変換ロジックが壊れても失敗しません。実際の GET 呼び出し経由の期待値検証に置き換えるのがよいです。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/sessions/__tests__/chrome-sidecar-response.test.ts` around lines
120 - 133, The test currently just spreads dbResult into response (const
response = { ...dbResult }) which doesn't exercise the API logic; change the
spec to call the real GET handler for /api/sessions/:id (or invoke the exported
GET function from the sessions route) while mocking the Drizzle query to return
dbResult, then assert the JSON response contains chrome_container_id and
chrome_debug_port; update the test in chrome-sidecar-response.test.ts to remove
the self-validation and perform an actual GET call (or handler invocation) with
the mocked DB so the conversion logic is exercised and regressions are caught.
src/services/__tests__/chrome-sidecar-cleanup-integration.test.ts (1)

35-40: server.ts の結線回帰をこのテストでは検知できません

このケースはテスト内の疑似フローを検証しており、実際の起動コードが cleanupOrphaned() を呼ばなくなっても通ってしまいます。起動フロー本体(または抽出した起動ヘルパー)を直接テスト対象にしてください。

Also applies to: 45-52

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/__tests__/chrome-sidecar-cleanup-integration.test.ts` around
lines 35 - 40, The test currently calls ChromeSidecarService.cleanupOrphaned()
directly which lets regressions in the real startup flow pass unnoticed; change
the test to exercise the actual startup flow (or an extracted startup helper)
used in server.ts so it will fail if server.ts stops calling cleanupOrphaned.
Specifically, locate the server startup entry (e.g., exported
startServer/initServer/bootstrap function) and invoke that in the test instead
of new ChromeSidecarService().cleanupOrphaned(), keep or inject the same mock
for ChromeSidecarService.cleanupOrphaned, and assert the mock was called; ensure
any required bootstrapping dependencies are stubbed/mocked so the startup
function can run in the test.
docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md (1)

185-229: 実装との差異について

設計書では docker exec + wget を使用したCDPヘルスチェックを記載していますが、実際の実装(chrome-sidecar-service.ts)ではホスト側ポート経由の fetch を使用しています。Line 229の注記で言及されている代替方式が採用されています。設計書と実装の整合性を保つため、この注記を設計本文に反映するか、コードブロック自体を更新することを検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md` around
lines 185 - 229, The design doc's CDP health-check block (waitForCDP) currently
shows docker exec + wget but the real implementation in
chrome-sidecar-service.ts uses host-port fetch; update the design: either
replace the code example in waitForCDP with the host-port fetch implementation
pattern (using fetch against the mapped host port or network IP) and remove the
wget-centric assumption, or keep the wget example but move the current note into
the main implementation section and explicitly document the alternate host-port
fetch approach used in chrome-sidecar-service.ts (include function name
waitForCDP and file chrome-sidecar-service.ts as references) so design and code
remain consistent.
src/services/chrome-sidecar-service.ts (1)

433-447: ポーリング間隔の重複に関する軽微な考慮事項

fetch のタイムアウト(interval = 1秒)と、その後の sleep(同じく1秒)が重複する可能性があります。fetch がタイムアウトした場合、実質的に1回のイテレーションに約2秒かかります。

ただし、CDPが応答し始めるとすぐに成功するため、実用上は大きな問題にはなりません。30秒のタイムアウトで十分な余裕があります。

♻️ 改善案(オプション)
     while (Date.now() - startTime < timeoutMs) {
       try {
         const controller = new AbortController();
         // 個々のfetchタイムアウト: ポーリング間隔以下に設定し重複を防ぐ
         const fetchTimeout = setTimeout(() => controller.abort(), interval);
         const response = await fetch(url, { signal: controller.signal });
         clearTimeout(fetchTimeout);
         if (response.ok) {
           return true;
         }
       } catch {
         // CDPがまだ準備中
       }
-      await new Promise(resolve => setTimeout(resolve, interval));
+      // fetchがタイムアウトした場合は既に1秒経過しているため、短いスリープで十分
+      await new Promise(resolve => setTimeout(resolve, 100));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/chrome-sidecar-service.ts` around lines 433 - 447, The loop can
double-wait because fetch is aborted after interval and then you always sleep
for interval; update the loop in chrome-sidecar-service.ts (symbols: startTime,
timeoutMs, interval, controller, fetchTimeout) so each iteration enforces a
single interval period: record iterationStart before creating the
AbortController, set the fetch timeout to abort after the full desired fetch
window, await fetch, then compute elapsed = Date.now() - iterationStart and only
sleep for Math.max(0, interval - elapsed) before the next iteration; this
ensures total time per loop is ~interval rather than ~2*interval while keeping
the existing timeoutMs logic intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md`:
- Line 9: 行内のカンマ区切りで並んでいる要件識別子(例: REQ-001-001, REQ-001-002, REQ-001-004,
REQ-002-001, REQ-002-002, REQ-004-001, NFR-SEC-001, NFR-SEC-002, NFR-RES-001,
NFR-RES-002,
NFR-RES-003)を1行の長いカンマ列からマークダウンの箇条書き(あるいは改行されたリスト)に置き換えてください;具体的には当該行を削除して各識別子を各行先頭にハイフンまたは番号付きリスト記号を付けて分割し、README/ドキュメントの他のリストと一致する書式(例:
"- REQ-001-001")に揃えてください。

In `@docs/sdd/design/chrome-sidecar/components/db-schema.md`:
- Around line 105-106: ドキュメントの chromeSidecar.image
の正規表現が実装と食い違っているため、ドキュメントを実装ルールに合わせて修正してください:chromeSidecar.image
の説明と正規表現を、レジストリポートで使用するポート表記を許可する形に更新し、現在の `[-a-z0-9._/]+` からコロン `:`
を許可する正規表現(例としてポートを含められる形)や説明文を反映させてください(参照シンボル:chromeSidecar.image)。

In `@docs/sdd/design/chrome-sidecar/components/environment-ui.md`:
- Around line 50-56: The design says to remove the chromeSidecar key from the
saved config when the sidecar toggle is OFF, but the implementation in
src/components/projects/ProjectEnvironmentSection.tsx currently writes
chromeSidecar: { enabled: false }; fix by updating the save/serialize logic in
ProjectEnvironmentSection (the handler that builds the config JSON) to omit the
chromeSidecar property entirely when the toggle is false (instead of setting
enabled: false); ensure any helper that merges config or functions named like
buildConfig/saveEnvironment respect this omission and update related tests or
API consumers to expect absence of chromeSidecar rather than an enabled:false
entry, or alternatively update the design doc to match the current
implementation if you prefer keeping enabled:false.

In `@server.ts`:
- Around line 337-345: Replace the direct instantiation new
ChromeSidecarService() with the singleton accessor
ChromeSidecarService.getInstance() so the startup path uses the same singleton
as the rest of the app; obtain the instance via
ChromeSidecarService.getInstance(), then call await instance.cleanupOrphaned()
and keep the existing try/catch and logger.error/logger.info calls (references:
ChromeSidecarService.getInstance, ChromeSidecarService.cleanupOrphaned, logger).

In `@src/components/environments/ChromeSidecarSection.tsx`:
- Around line 34-48: The checkbox inside ChromeSidecarSection lacks an
accessible name; update the input (the checkbox element that uses
checked={enabled}, onChange={(e) => onEnabledChange(e.target.checked)},
disabled={disabled}) to provide one—either add aria-label="Enable Chrome
Sidecar" (or a localized string) or add aria-labelledby linking to the section
heading (give the h5 a unique id and set input aria-labelledby to that id) so
screen readers can announce the control; ensure the chosen string clearly
conveys the toggle purpose and keep the existing label wrapper/visual markup
unchanged.

In `@src/components/projects/ProjectEnvironmentSection.tsx`:
- Around line 155-160: When restoring chromeSidecar settings in
ProjectEnvironmentSection, explicitly reset state to defaults whenever
config.chromeSidecar is missing, parse fails, or the environment/container type
is not DOCKER: call setChromeSidecarEnabled(false), setChromeSidecarImage('')
and setChromeSidecarTag('') instead of leaving previous values; likewise update
the other restore/parse block that touches chromeSidecar to do the same. Ensure
saving/serialization logic only writes image/tag when chromeSidecar is enabled
so stale values are not persisted.

In `@src/lib/chrome-sidecar-validator.ts`:
- Around line 16-24: The code treats any falsy chromeSidecar as "not set" and
lets invalid values like false/0/'' pass; change the existence check to
distinguish "key not present" from "present but falsy": first check if config
hasOwnProperty('chromeSidecar') or use 'chromeSidecar' in config to decide if
it's provided; then ensure chromeSidecar is an object (typeof chromeSidecar ===
'object' && chromeSidecar !== null) before casting to sidecar; finally validate
sidecar.enabled exists and is a boolean (and keep the early return when
sidecar.enabled === false), referencing the config/chromeSidecar variable and
the sidecar.enabled check in your existing logic.

In `@src/services/__tests__/chrome-sidecar-cleanup-integration.test.ts`:
- Around line 63-78: The test sets serverStarted = true unconditionally, so it
cannot detect whether cleanupOrphaned swallowed the Docker ENOENT error; update
the test for ChromeSidecarService.cleanupOrphaned to (1) remove the
unconditional assignment and instead set a flag only when execution reaches the
post-call path or assert that the call does not throw (e.g., await
expect(sidecarService.cleanupOrphaned()).resolves.toBeUndefined()), and (2)
verify logger.error behavior by asserting logger.error was (or was not) called
as intended (use mock/spy on logger.error and
expect(mockedLogger.error).toHaveBeenCalledWith(...) or toHaveBeenCalledTimes(0)
depending on desired behavior). Also keep the
mockCleanupOrphaned.mockRejectedValueOnce call to simulate the ENOENT error.

In `@src/services/adapters/__tests__/docker-adapter-chrome-sidecar.test.ts`:
- Around line 30-240: The tests currently call mocks directly or reimplement
logic, so they don't exercise the real control flow; change the spec to invoke
DockerAdapter.createSession and DockerAdapter.destroySession (and
PTYSessionManager.createSession where applicable) instead of calling
mockStartSidecar/mockStopSidecar or reimplementing injectBrowserUrl logic, and
mock the external dependencies (DockerClient, DB, ChromeSidecarService) to
return controlled values—specifically stub ChromeSidecarService.startSidecar,
ChromeSidecarService.stopSidecar, and DockerAdapter.connectClaudeContainer (or
DockerClient methods) while asserting the adapter/manager methods call those
services and produce the expected behavior and error-handling (graceful
degradation) rather than asserting on the mocks directly.

In `@src/services/adapters/docker-adapter.ts`:
- Around line 1001-1017: The DB write for chrome_container_id/debug_port can
fail and later teardown (destroySession) relies solely on the DB, so modify the
DockerAdapter flow to also persist the sidecar identifiers directly on the
in-memory DockerSession object (e.g., set DockerSession.containerName and
DockerSession.networkName and debugPort) immediately after sidecar creation, and
ensure destroySession() reads these properties first (falling back to the DB
only if they are absent); update the code paths around the DB update (the
try/catch block that updates schema.sessions) to still log the error but keep
the in-memory DockerSession populated so teardown can always find the container
even if the DB write failed.
- Around line 841-865: createSession starts the Chrome sidecar via
ChromeSidecarService.startSidecar (producing sidecarResult and container/network
named with sessionId) but never calls stopSidecar on subsequent failures,
leaking cw-chrome-<sessionId> and cw-net-<sessionId>; wrap the code that runs
after startSidecar (the createContainer/attach/start/waitForContainerReady/DB
update sequence) in a try/catch/finally (or ensure a Promise.finally) so that on
any exception or non-success path you call
ChromeSidecarService.getInstance().stopSidecar(sessionId, sidecarResult) (only
if sidecarResult is set) to tear down container/network, and apply the same fix
to the other occurrence around the block at lines ~1150-1156 to guarantee
cleanup on all failure paths.

---

Nitpick comments:
In `@docs/sdd/design/chrome-sidecar/api/environment-api.md`:
- Line 45: The validation regex for chromeSidecar.image currently listed as
`/^[-a-z0-9._/]+$/` rejects colon characters used for registry ports/tags;
update the pattern referenced for chromeSidecar.image to allow colons (add ':'
to the allowed character class) and update the associated error message (the
string describing allowed characters, e.g., "lowercase alphanumeric, dots,
slashes, hyphens") to mention colons (or "ports/tags") so the docs and message
reflect the new acceptance of colon-separated registry:port formats.

In `@docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md`:
- Around line 185-229: The design doc's CDP health-check block (waitForCDP)
currently shows docker exec + wget but the real implementation in
chrome-sidecar-service.ts uses host-port fetch; update the design: either
replace the code example in waitForCDP with the host-port fetch implementation
pattern (using fetch against the mapped host port or network IP) and remove the
wget-centric assumption, or keep the wget example but move the current note into
the main implementation section and explicitly document the alternate host-port
fetch approach used in chrome-sidecar-service.ts (include function name
waitForCDP and file chrome-sidecar-service.ts as references) so design and code
remain consistent.

In `@docs/sdd/design/chrome-sidecar/index.md`:
- Around line 7-25: The fenced ASCII art block in index.md triggers markdownlint
MD040; update the code fence that wraps the Session起動フロー ASCII diagram to
include a plain text language tag (e.g., use ```text or ```plaintext) so the
block is explicitly marked as non-code and the linter warning is
suppressed—locate the ASCII diagram block (the fenced block containing
"Session起動フロー (サイドカー有効時):" and the diagram) and add the language identifier to
the opening fence.

In `@docs/sdd/design/chrome-sidecar/sequences/session-teardown.md`:
- Around line 13-68: Add a language specifier "text" to the fenced sequence
diagram code block (the triple-backtick block that starts with the diagram
header line "Client          PTYSessionMgr     DockerAdapter ...") to silence
markdownlint MD040; update the opening fence from ``` to ```text (and apply the
same change to any other similar fenced-diagram blocks in session-teardown.md)
so the diagram remains unchanged but is explicitly marked as plain text.

In `@src/app/api/environments/__tests__/chrome-sidecar-validation.test.ts`:
- Around line 6-103: Add tests to cover cases where chromeSidecar key exists but
is not an object (e.g., chromeSidecar: false, 0, '', []) by calling
validateChromeSidecarConfig with each and asserting the returned error is not
null and mentions "chromeSidecar" (or "chromeSidecar.enabled" depending on
current error text); locate the test block using the existing describe for
chrome-sidecar validation and the validateChromeSidecarConfig calls to add these
new it(...) cases to prevent regressions for non-object types.

In `@src/app/api/projects/`[project_id]/environment/route.ts:
- Around line 216-228: The code duplicates computation of effectiveType (using
type ?? existing.type) in multiple places; compute effectiveType once at the
start of the config validation block and reuse it for all subsequent checks
(including the DOCKER-only chromeSidecar call), then remove the second
calculation to improve readability—update references in the chromeSidecar
validation to use the single effectiveType and ensure
validateChromeSidecarConfig(sanitizedConfig) remains unchanged.

In `@src/app/api/sessions/__tests__/chrome-sidecar-response.test.ts`:
- Around line 120-133: The test currently just spreads dbResult into response
(const response = { ...dbResult }) which doesn't exercise the API logic; change
the spec to call the real GET handler for /api/sessions/:id (or invoke the
exported GET function from the sessions route) while mocking the Drizzle query
to return dbResult, then assert the JSON response contains chrome_container_id
and chrome_debug_port; update the test in chrome-sidecar-response.test.ts to
remove the self-validation and perform an actual GET call (or handler
invocation) with the mocked DB so the conversion logic is exercised and
regressions are caught.

In `@src/components/environments/__tests__/chrome-sidecar-form.test.tsx`:
- Around line 1-72: Add two unit tests in the existing
chrome-sidecar-form.test.tsx to assert that ChromeSidecarSection calls the
provided callbacks when inputs change: render ChromeSidecarSection with
enabled={true} and a mock onImageChange, simulate changing the "Chrome Image"
input (fireEvent.change) and expect onImageChange
toHaveBeenCalledWith('new-image'); similarly render with a mock onTagChange,
change the "Chrome Tag" input and expect onTagChange
toHaveBeenCalledWith('132.0.0.0'); use the existing defaultProps and
screen.getByLabelText to locate inputs and vi.fn() for mocks.

In `@src/components/sessions/ChromeDebugInfo.tsx`:
- Around line 28-34: The code currently treats chromeDebugPort as truthy/falsy
which will treat 0 as unavailable; change both checks to explicit null
comparisons (use chromeDebugPort !== null for the ternary that shows 'Running'
vs 'Running (debug port unavailable)' and for the conditional rendering that
shows the debug-port div) so that a port value of 0 is handled according to the
type intent and only null indicates "unavailable."

In `@src/services/__tests__/chrome-sidecar-cleanup-integration.test.ts`:
- Around line 35-40: The test currently calls
ChromeSidecarService.cleanupOrphaned() directly which lets regressions in the
real startup flow pass unnoticed; change the test to exercise the actual startup
flow (or an extracted startup helper) used in server.ts so it will fail if
server.ts stops calling cleanupOrphaned. Specifically, locate the server startup
entry (e.g., exported startServer/initServer/bootstrap function) and invoke that
in the test instead of new ChromeSidecarService().cleanupOrphaned(), keep or
inject the same mock for ChromeSidecarService.cleanupOrphaned, and assert the
mock was called; ensure any required bootstrapping dependencies are
stubbed/mocked so the startup function can run in the test.

In `@src/services/chrome-sidecar-service.ts`:
- Around line 433-447: The loop can double-wait because fetch is aborted after
interval and then you always sleep for interval; update the loop in
chrome-sidecar-service.ts (symbols: startTime, timeoutMs, interval, controller,
fetchTimeout) so each iteration enforces a single interval period: record
iterationStart before creating the AbortController, set the fetch timeout to
abort after the full desired fetch window, await fetch, then compute elapsed =
Date.now() - iterationStart and only sleep for Math.max(0, interval - elapsed)
before the next iteration; this ensures total time per loop is ~interval rather
than ~2*interval while keeping the existing timeoutMs logic intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ae3a053b-63b7-4b6c-9a96-bfc62bbd83d8

📥 Commits

Reviewing files that changed from the base of the PR and between 4df7c05 and 8328ece.

📒 Files selected for processing (35)
  • docs/sdd/design/chrome-sidecar/api/environment-api.md
  • docs/sdd/design/chrome-sidecar/api/session-api.md
  • docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md
  • docs/sdd/design/chrome-sidecar/components/db-schema.md
  • docs/sdd/design/chrome-sidecar/components/docker-adapter.md
  • docs/sdd/design/chrome-sidecar/components/environment-ui.md
  • docs/sdd/design/chrome-sidecar/index.md
  • docs/sdd/design/chrome-sidecar/sequences/cleanup.md
  • docs/sdd/design/chrome-sidecar/sequences/session-startup.md
  • docs/sdd/design/chrome-sidecar/sequences/session-teardown.md
  • drizzle/0001_youthful_naoko.sql
  • drizzle/meta/0001_snapshot.json
  • drizzle/meta/_journal.json
  • server.ts
  • src/app/api/environments/__tests__/chrome-sidecar-validation.test.ts
  • src/app/api/projects/[project_id]/environment/route.ts
  • src/app/api/sessions/__tests__/chrome-sidecar-response.test.ts
  • src/components/__tests__/session-chrome-info.test.tsx
  • src/components/environments/ChromeSidecarSection.tsx
  • src/components/environments/__tests__/chrome-sidecar-form.test.tsx
  • src/components/projects/ProjectEnvironmentSection.tsx
  • src/components/sessions/ChromeBadge.tsx
  • src/components/sessions/ChromeDebugInfo.tsx
  • src/db/__tests__/schema-chrome-sidecar.test.ts
  • src/db/schema.ts
  • src/lib/chrome-sidecar-validator.ts
  • src/services/__tests__/chrome-sidecar-cleanup-integration.test.ts
  • src/services/__tests__/chrome-sidecar-service.test.ts
  • src/services/adapters/__tests__/docker-adapter-chrome-sidecar.test.ts
  • src/services/adapters/docker-adapter.ts
  • src/services/chrome-sidecar-service.ts
  • src/services/environment-adapter.ts
  • src/services/pty-session-manager.ts
  • src/types/__tests__/chrome-sidecar-config.test.ts
  • src/types/environment.ts

Comment thread docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md Outdated
Comment thread docs/sdd/design/chrome-sidecar/components/db-schema.md Outdated
Comment thread docs/sdd/design/chrome-sidecar/components/environment-ui.md
Comment thread server.ts
Comment thread src/components/environments/ChromeSidecarSection.tsx
Comment thread src/lib/chrome-sidecar-validator.ts Outdated
Comment thread src/services/__tests__/chrome-sidecar-cleanup-integration.test.ts
Comment thread src/services/adapters/__tests__/docker-adapter-chrome-sidecar.test.ts Outdated
Comment thread src/services/adapters/docker-adapter.ts
Comment thread src/services/adapters/docker-adapter.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md`:
- Around line 234-249: The fenced code block containing the startup steps (the
block starting with "サーバー起動時:" in chrome-sidecar-service.md) lacks a language
tag and triggers MD040; update the opening fence from ``` to a
language-specified fence such as ```text (or another appropriate language) so
the code block becomes ```text ... ``` and the linter warning is resolved.
- Around line 210-214:
ドキュメント内のコード例が実装と乖離しているので、DockerClient.getInstance().getContainer(...)/container.exec(...)での
wget 実行例を削除し、実装で使っているホスト側ポート経由の HTTP GET ポーリング方式(Node.js の fetch または http.get
を用いるロジック)に差し替えてください。具体的には DockerClient.getInstance().getContainer と
container.exec を参照する箇所を見つけ、コンテナ内コマンド実行例ではなく、ホストの
http://localhost:9222/json/version
に対するポーリング(リトライ、タイムアウト、レスポンスチェック)を行う擬似コード例に置き換え、230行の注記と整合する説明に更新してください。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e9706260-2556-45f7-bf15-7ecd8c73fd11

📥 Commits

Reviewing files that changed from the base of the PR and between 8328ece and ab60019.

📒 Files selected for processing (2)
  • docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md
  • scripts/test-migration-ci.ts

Comment on lines +210 to +214
// docker exec でcurlやwgetの代わりにNode.js http.getを使用
const container = DockerClient.getInstance().getContainer(containerName);
const exec = await container.exec({
Cmd: ['wget', '-q', '-O', '-', 'http://localhost:9222/json/version'],
AttachStdout: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 実装と設計の整合性確認(read-only)
# 期待: chrome-sidecar-service.ts の実装方式(wget実行 or http.get)と設計書が一致していること
fd chrome-sidecar-service.ts
rg -n -C3 "waitForCDP|json/version|wget|http.get|exec\\(" src docs

Repository: windschord/claude-work

Length of output: 50380


設計ドキュメントのコード例を実装に合わせて更新してください。

210行目のコメントは「Node.js http.get」と正しく説明していますが、直後のコード例は wget 実行になっています。実装では既にホスト側ポート経由の HTTP GET ポーリング方式(fetch 使用)に統一されており、210-214行のコード例もこれに合わせて修正が必要です。230行の注記は実装判断を反映しているため、コード例が実装と乖離している状況です。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md` around
lines 210 - 214,
ドキュメント内のコード例が実装と乖離しているので、DockerClient.getInstance().getContainer(...)/container.exec(...)での
wget 実行例を削除し、実装で使っているホスト側ポート経由の HTTP GET ポーリング方式(Node.js の fetch または http.get
を用いるロジック)に差し替えてください。具体的には DockerClient.getInstance().getContainer と
container.exec を参照する箇所を見つけ、コンテナ内コマンド実行例ではなく、ホストの
http://localhost:9222/json/version
に対するポーリング(リトライ、タイムアウト、レスポンスチェック)を行う擬似コード例に置き換え、230行の注記と整合する説明に更新してください。

Comment thread docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md Outdated
- browserUrlをws://からhttp://に変更(要件/設計準拠)
- stopSidecar()に成功/失敗戻り値追加、失敗時はDB参照保持
- network disconnect追加(remove前のbest-effort切断)
- cleanupOrphaned Phase 2に孤立ネットワーク回収追加
- createSession失敗時のサイドカークリーンアップ追加
- DB書き込み失敗時のsidecar停止経路確保
- latestタグバリデーションをcase-insensitive/trim対応
- アクセシビリティ・UIステート初期化・バリデーション強化
- 設計書の不整合修正(環境UI、DBスキーマ、サービス設計書)
- server.tsでシングルトンgetInstance()使用に修正
- テストをすべての変更に合わせて更新

Refs #226

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 21, 2026 11:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/components/projects/ProjectEnvironmentSection.tsx (1)

155-177: ⚠️ Potential issue | 🟠 Major

Chrome Sidecar復元処理で状態リークが残っています。

config.chromeSidecar 未設定時のリセットは入っていますが、env.type !== 'DOCKER'env.config なし・JSONパース失敗時(Line 173-177)では sidecar state がリセットされず、前回読込値を保持する経路が残っています。結果として、同一コンポーネントで環境切替後に意図しない値を再保存する可能性があります。

💡 修正案(復元を全分岐で決定的にする)
+const resetChromeSidecarState = () => {
+  setChromeSidecarEnabled(false);
+  setChromeSidecarImage('chromium/headless-shell');
+  setChromeSidecarTag('131.0.6778.204');
+};

 if (env.type === 'DOCKER' && env.config) {
   try {
     const config: EnvironmentConfig = typeof env.config === 'string'
       ? JSON.parse(env.config)
       : env.config;
@@
-    if (config.chromeSidecar) {
-      setChromeSidecarEnabled(config.chromeSidecar.enabled ?? false);
-      if (config.chromeSidecar.image) setChromeSidecarImage(config.chromeSidecar.image);
-      if (config.chromeSidecar.tag) setChromeSidecarTag(config.chromeSidecar.tag);
-    } else {
-      setChromeSidecarEnabled(false);
-      setChromeSidecarImage('chromium/headless-shell');
-      setChromeSidecarTag('131.0.6778.204');
-    }
+    setChromeSidecarEnabled(config.chromeSidecar?.enabled ?? false);
+    setChromeSidecarImage(config.chromeSidecar?.image?.trim() || 'chromium/headless-shell');
+    setChromeSidecarTag(config.chromeSidecar?.tag?.trim() || '131.0.6778.204');
@@
   } catch {
     setImageName('ghcr.io/windschord/claude-work-sandbox');
     setImageTag('latest');
+    resetChromeSidecarState();
   }
+} else {
+  resetChromeSidecarState();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/projects/ProjectEnvironmentSection.tsx` around lines 155 -
177, The chromeSidecar state can leak because only the config.chromeSidecar
true/false branch resets it; ensure you deterministically reset chrome sidecar
state in every control path (env.type !== 'DOCKER' branch, missing env.config
branch, and the catch block for JSON parse). Update the logic around the
try/catch in the ProjectEnvironmentSection component so that
setChromeSidecarEnabled, setChromeSidecarImage, and setChromeSidecarTag are
always called with explicit defaults when Docker config is absent or parse fails
(rather than leaving previous values), keeping the existing restore behavior
when config.chromeSidecar exists; reference the existing setters
setChromeSidecarEnabled, setChromeSidecarImage, setChromeSidecarTag and the
try/catch block that sets initialConfigRef.current.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/sdd/design/chrome-sidecar/components/db-schema.md`:
- Line 115: The sentence about backward compatibility is ambiguous: update the
wording that references DockerAdapterConfig and AdapterFactory so it states they
ignore the chromeSidecar key only when that key is absent (i.e., treat
chromeSidecar as disabled if not present), not that they always ignore it;
replace the phrase "chromeSidecar キーを無視する" with a precise clause like
"chromeSidecar が未設定の場合のみ無効扱い(明示的に設定・参照される場合は従う)" and ensure DockerAdapterConfig
and AdapterFactory are explicitly mentioned as honoring an explicitly set
chromeSidecar.

---

Duplicate comments:
In `@src/components/projects/ProjectEnvironmentSection.tsx`:
- Around line 155-177: The chromeSidecar state can leak because only the
config.chromeSidecar true/false branch resets it; ensure you deterministically
reset chrome sidecar state in every control path (env.type !== 'DOCKER' branch,
missing env.config branch, and the catch block for JSON parse). Update the logic
around the try/catch in the ProjectEnvironmentSection component so that
setChromeSidecarEnabled, setChromeSidecarImage, and setChromeSidecarTag are
always called with explicit defaults when Docker config is absent or parse fails
(rather than leaving previous values), keeping the existing restore behavior
when config.chromeSidecar exists; reference the existing setters
setChromeSidecarEnabled, setChromeSidecarImage, setChromeSidecarTag and the
try/catch block that sets initialConfigRef.current.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1ff92ff2-056e-4bb6-954b-15518bd70fc3

📥 Commits

Reviewing files that changed from the base of the PR and between ab60019 and 863e2ec.

📒 Files selected for processing (12)
  • docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md
  • docs/sdd/design/chrome-sidecar/components/db-schema.md
  • docs/sdd/design/chrome-sidecar/components/environment-ui.md
  • server.ts
  • src/components/environments/ChromeSidecarSection.tsx
  • src/components/projects/ProjectEnvironmentSection.tsx
  • src/lib/chrome-sidecar-validator.ts
  • src/services/__tests__/chrome-sidecar-cleanup-integration.test.ts
  • src/services/__tests__/chrome-sidecar-service.test.ts
  • src/services/adapters/__tests__/docker-adapter-chrome-sidecar.test.ts
  • src/services/adapters/docker-adapter.ts
  • src/services/chrome-sidecar-service.ts
✅ Files skipped from review due to trivial changes (4)
  • docs/sdd/design/chrome-sidecar/components/environment-ui.md
  • src/services/tests/chrome-sidecar-service.test.ts
  • src/services/adapters/tests/docker-adapter-chrome-sidecar.test.ts
  • src/services/chrome-sidecar-service.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/lib/chrome-sidecar-validator.ts
  • src/components/environments/ChromeSidecarSection.tsx
  • src/services/tests/chrome-sidecar-cleanup-integration.test.ts
  • src/services/adapters/docker-adapter.ts

Comment thread docs/sdd/design/chrome-sidecar/components/db-schema.md Outdated

This comment was marked as outdated.

windschord and others added 2 commits March 21, 2026 20:13
chromeSidecar キー未存在時のみ無効扱いとする記述に修正

Refs #226

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- stopSidecar: AutoRemoveコンテナの404/304を成功扱いに
- debugPort未取得時はCDPチェックスキップしサイドカー成功で継続
- pty-session-manager: chromeSidecar設定のランタイムバリデーション追加
- connectClaudeContainer失敗時はサイドカー停止しDB保存しない
- session-startup.md: ネットワーク作成失敗のみ中止と明記
- バリデーターのコメント修正

Refs #226

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 21, 2026 11:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1411 to +1415
createOptions.Cmd = [
mcpInjectScript + ' && exec "$@"',
'--',
...allArgs,
];

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

injectBrowserUrl で組み立てている mcpInjectScript は && で連結されており、node -e や .mcp.json 書き込みが失敗すると exec "$@" まで到達せずコンテナ起動自体が失敗します。Chrome Sidecar は graceful degradation を意図しているので、注入処理は失敗してもセッション起動を継続できるように(例: 失敗時に || true で握りつぶす / エラー時はログだけ出して続行する)非致命化した方が安全です。

Copilot uses AI. Check for mistakes.
Comment on lines +367 to +375
// 停止済みまたは存在しないコンテナ: DB更新とネットワーク削除
try {
const networkName = this.getNetworkName(session.id);
const network = docker.getDockerInstance().getNetwork(networkName);
await network.remove();
} catch {
// ネットワークも既に削除済みの場合がある
}

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanupOrphaned の Phase 1 でネットワーク削除に失敗しても(接続中コンテナが残っている等)例外を握りつぶしたまま DB の chrome_container_id/chrome_debug_port を NULL に更新しています。この状態だと destroySession 側で stopSidecar が呼ばれず、cw-net- が残留し続ける可能性があります。ネットワーク側を inspect→disconnect(best-effort) してから remove するか、少なくともネットワーク削除に成功した場合のみ DB をクリアするなど、DB と実リソースの整合性が崩れないようにしてください。

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +39
// server.ts の起動フローを再現
const sidecarService = new ChromeSidecarService();
await sidecarService.cleanupOrphaned();

expect(mockCleanupOrphaned).toHaveBeenCalledTimes(1);

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

server.ts 側の変更は ChromeSidecarService.getInstance().cleanupOrphaned() を try/catch で呼ぶ形ですが、このテストは server.ts を通さず new ChromeSidecarService() を直接呼んでおり、シングルトン(getInstance)経由の呼び出しや server.ts のエラーハンドリングを実際には検証できていません。server.ts の該当処理を関数として切り出して import してテストするか、少なくとも getInstance をモックして getInstance().cleanupOrphaned が呼ばれることを検証してください。

Copilot uses AI. Check for mistakes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/lib/chrome-sidecar-validator.ts (2)

41-43: 冗長な型チェック

Line 37 で既に typeof sidecar.tag !== 'string' を検証しているため、Line 41 の typeof sidecar.tag === 'string' チェックは冗長です。

♻️ 簡略化の提案
- if (typeof sidecar.tag === 'string' && sidecar.tag.trim().toLowerCase() === 'latest') {
+ if (sidecar.tag.trim().toLowerCase() === 'latest') {
    return 'chromeSidecar.tag must be a specific version (latest is not allowed for reproducibility)';
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/chrome-sidecar-validator.ts` around lines 41 - 43, Remove the
redundant typeof check in the conditional that rejects "latest": since you
already validate typeof sidecar.tag !== 'string' earlier, simplify the if to
directly test sidecar.tag.trim().toLowerCase() === 'latest' and return the same
error message ("chromeSidecar.tag must be a specific version (latest is not
allowed for reproducibility)"); update the conditional around sidecar.tag so it
relies on the prior type guard and does not repeat typeof sidecar.tag ===
'string'.

33-35: エラーメッセージと正規表現の不整合

正規表現 [-a-z0-9._/:] はアンダースコア _ を許可していますが、エラーメッセージには記載されていません。ユーザーの混乱を避けるため、メッセージを更新することを推奨します。

🔧 修正案
  if (typeof sidecar.image !== 'string' || !/^[-a-z0-9._/:]+$/.test(sidecar.image)) {
-   return 'chromeSidecar.image must be a valid Docker image name (lowercase alphanumeric, dots, slashes, colons, hyphens)';
+   return 'chromeSidecar.image must be a valid Docker image name (lowercase alphanumeric, dots, underscores, slashes, colons, hyphens)';
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/chrome-sidecar-validator.ts` around lines 33 - 35, The error message
for the image validation in chrome-sidecar-validator.ts is inconsistent with the
regex: the pattern for sidecar.image (checked in the if that tests typeof
sidecar.image and /^[-a-z0-9._/:]+$/) allows underscores but the message does
not mention them; update the returned string in that validation branch to list
underscores (e.g., include "underscore") or otherwise match the regex (or
alternatively remove `_` from the regex) so the message and the regexp agree,
referencing the sidecar.image validation in chrome-sidecar-validator.ts.
src/services/adapters/docker-adapter.ts (1)

984-1047: グレースフルデグラデーション時の挙動について確認

connectClaudeContainer 失敗時(994-1013行)または DB 書き込み失敗時(1027-1044行)にサイドカーをクリーンアップしていますが、この時点で Claude コンテナは既に起動しており、.mcp.json には browserUrl が注入済みです。

結果として、chrome-devtools MCP サーバーは存在しない Chrome コンテナへの接続を試みて失敗します。Claude Code 自体は動作を継続しますが、ユーザーには MCP 接続エラーが表示される可能性があります。

これは設計として意図されたグレースフルデグラデーションだと理解していますが、運用上のトラブルシューティングのため、この挙動をドキュメントに記載することを検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/adapters/docker-adapter.ts` around lines 984 - 1047, The review
notes that when ChromeSidecarService.connectClaudeContainer (used here) or the
DB update fails we best-effort stop the sidecar while the Claude container
remains running and .mcp.json already has browserUrl injected, causing MCP
clients to attempt connecting to a non-existent chrome-devtools endpoint;
document this graceful-degradation behavior. Update the operational/README (or
runbook) to explain: where ChromeSidecarService.connectClaudeContainer and
stopSidecar are invoked, that .mcp.json/browserUrl may be present even if
sidecar cleanup occurred, the observable symptoms (MCP connection errors with
sessionId), recommended troubleshooting steps (check logs for logger.warn
entries, verify container existence, inspect session DB fields
chrome_container_id/chrome_debug_port), and suggested mitigation (re-run sidecar
or restart session). Ensure the doc references the functions
ChromeSidecarService.connectClaudeContainer, ChromeSidecarService.stopSidecar
and the DB fields chrome_container_id/chrome_debug_port so operators can
correlate logs and state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/sdd/design/chrome-sidecar/sequences/session-startup.md`:
- Line 13: Add a language identifier (e.g., "text") to all fenced code blocks
that currently lack one in this document—specifically the sequence diagram/code
fences that start with role lines like "Client          PTYSessionMgr    
DockerAdapter     ChromeSidecarSvc    DockerClient" and the similar blocks
containing "DockerAdapter     ChromeSidecarSvc    DockerClient"; update each
opening ``` to ```text so markdownlint MD040 is resolved (apply same change to
the other occurrences mentioned in the review).

---

Nitpick comments:
In `@src/lib/chrome-sidecar-validator.ts`:
- Around line 41-43: Remove the redundant typeof check in the conditional that
rejects "latest": since you already validate typeof sidecar.tag !== 'string'
earlier, simplify the if to directly test sidecar.tag.trim().toLowerCase() ===
'latest' and return the same error message ("chromeSidecar.tag must be a
specific version (latest is not allowed for reproducibility)"); update the
conditional around sidecar.tag so it relies on the prior type guard and does not
repeat typeof sidecar.tag === 'string'.
- Around line 33-35: The error message for the image validation in
chrome-sidecar-validator.ts is inconsistent with the regex: the pattern for
sidecar.image (checked in the if that tests typeof sidecar.image and
/^[-a-z0-9._/:]+$/) allows underscores but the message does not mention them;
update the returned string in that validation branch to list underscores (e.g.,
include "underscore") or otherwise match the regex (or alternatively remove `_`
from the regex) so the message and the regexp agree, referencing the
sidecar.image validation in chrome-sidecar-validator.ts.

In `@src/services/adapters/docker-adapter.ts`:
- Around line 984-1047: The review notes that when
ChromeSidecarService.connectClaudeContainer (used here) or the DB update fails
we best-effort stop the sidecar while the Claude container remains running and
.mcp.json already has browserUrl injected, causing MCP clients to attempt
connecting to a non-existent chrome-devtools endpoint; document this
graceful-degradation behavior. Update the operational/README (or runbook) to
explain: where ChromeSidecarService.connectClaudeContainer and stopSidecar are
invoked, that .mcp.json/browserUrl may be present even if sidecar cleanup
occurred, the observable symptoms (MCP connection errors with sessionId),
recommended troubleshooting steps (check logs for logger.warn entries, verify
container existence, inspect session DB fields
chrome_container_id/chrome_debug_port), and suggested mitigation (re-run sidecar
or restart session). Ensure the doc references the functions
ChromeSidecarService.connectClaudeContainer, ChromeSidecarService.stopSidecar
and the DB fields chrome_container_id/chrome_debug_port so operators can
correlate logs and state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b29621c-ade1-42e0-ba4b-1ac261eb9704

📥 Commits

Reviewing files that changed from the base of the PR and between afba23c and 2b45e03.

📒 Files selected for processing (6)
  • docs/sdd/design/chrome-sidecar/sequences/session-startup.md
  • src/lib/chrome-sidecar-validator.ts
  • src/services/__tests__/chrome-sidecar-service.test.ts
  • src/services/adapters/docker-adapter.ts
  • src/services/chrome-sidecar-service.ts
  • src/services/pty-session-manager.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/services/pty-session-manager.ts
  • src/services/tests/chrome-sidecar-service.test.ts


## 正常系フロー

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

フェンスドコードブロックに言語指定を追加してください。

markdownlint (MD040) 警告どおり、コードフェンスに言語が未指定です。レンダリング/静的チェック安定化のため、text などを明示してください。

修正例(diff)
-```
+```text
 Client          PTYSessionMgr     DockerAdapter     ChromeSidecarSvc    DockerClient
 ...
-```
+```

-```
+```text
 DockerAdapter     ChromeSidecarSvc    DockerClient
 ...
-```
+```

-```
+```text
 DockerAdapter     ChromeSidecarSvc    DockerClient
 ...
-```
+```

Also applies to: 87-87, 135-135

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 13-13: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/sdd/design/chrome-sidecar/sequences/session-startup.md` at line 13, Add
a language identifier (e.g., "text") to all fenced code blocks that currently
lack one in this document—specifically the sequence diagram/code fences that
start with role lines like "Client          PTYSessionMgr     DockerAdapter    
ChromeSidecarSvc    DockerClient" and the similar blocks containing
"DockerAdapter     ChromeSidecarSvc    DockerClient"; update each opening ``` to
```text so markdownlint MD040 is resolved (apply same change to the other
occurrences mentioned in the review).

windschord and others added 2 commits March 21, 2026 21:14
- バリデーターの境界値テスト追加(null/undefined/非オブジェクト型)
- エラーメッセージ内容の完全一致検証追加(StringLiteral対策)
- コンテナ名・ネットワーク名フォーマット検証
- セキュリティ設定値の具体的検証(CapDrop, SecurityOpt, Memory, AutoRemove)
- Dockerラベル・ポートバインディングの正確な値検証
- 各条件分岐の両パスのテスト追加(ConditionalExpression対策)
- 非Errorオブジェクトのエラーハンドリングパス追加
- stopSidecarのネットワークdisconnect/エラー結合テスト追加
- cleanupOrphanedのPhase 2b孤立ネットワーク回収テスト追加
- CDPヘルスチェックのタイムアウトメッセージ内容検証

Refs #226

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DockerAdapterのメソッドを実際に呼び出すテストに改善し、
mutation-testスコアを閾値以上に改善

Refs #226

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 21, 2026 12:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/services/adapters/__tests__/docker-adapter-chrome-sidecar.test.ts`:
- Line 247: The variable mockContainer created by calling setupContainerMock()
is unused and causes a no-unused-vars lint failure; either remove the assignment
statement entirely or, if the mock was intended for later use, reference it
where needed (e.g., pass mockContainer into the test helper or assertion).
Locate the unused declaration `const mockContainer = setupContainerMock()` in
the test and either delete that line or update the test to use `mockContainer`
(or rename to _mockContainer to indicate intentional unused) so the linter error
is resolved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: afb7a5c0-cae7-4ea8-9e72-16a8aa5ef8e6

📥 Commits

Reviewing files that changed from the base of the PR and between f37a544 and 21087e6.

📒 Files selected for processing (1)
  • src/services/adapters/__tests__/docker-adapter-chrome-sidecar.test.ts

Comment thread src/services/adapters/__tests__/docker-adapter-chrome-sidecar.test.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// null/undefinedは省略と同じ扱いで許容(キー存在するが値なし)
if (config.chromeSidecar === null || config.chromeSidecar === undefined) return null;
if (typeof config.chromeSidecar !== 'object') {

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chromeSidecar の型チェックが typeof === 'object' だけだと配列も通過してしまい、エラーメッセージが enabled の型不一致になって意図とズレます。Array.isArray(config.chromeSidecar) も明示的に弾いて、配列の場合も "chromeSidecar must be an object" を返すようにしてください。

Suggested change
if (typeof config.chromeSidecar !== 'object') {
if (typeof config.chromeSidecar !== 'object' || Array.isArray(config.chromeSidecar)) {

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +35
if (typeof sidecar.image !== 'string' || !/^[-a-z0-9._/:]+$/.test(sidecar.image)) {
return 'chromeSidecar.image must be a valid Docker image name (lowercase alphanumeric, dots, slashes, colons, hyphens)';
}

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chromeSidecar.image の正規表現が : を許容しているため、ユーザーが image 側にタグ(例: chromium/headless-shell:stable)や digest を含めても通ってしまい、実行時に ${image}:${tag}...:stable:131... のような不正な参照になります。image にはタグ/ digest を含められないようにするか、「最後の / 以降に : があれば不正」といったルールで分離してください。

Suggested change
if (typeof sidecar.image !== 'string' || !/^[-a-z0-9._/:]+$/.test(sidecar.image)) {
return 'chromeSidecar.image must be a valid Docker image name (lowercase alphanumeric, dots, slashes, colons, hyphens)';
}
const image = sidecar.image;
if (typeof image !== 'string' || !/^[-a-z0-9._/:]+$/.test(image)) {
return 'chromeSidecar.image must be a valid Docker image name (lowercase alphanumeric, dots, slashes, colons, hyphens)';
}
// image にはタグ(:tag)や digest(@digest)を含めない。
// レジストリのポート指定のための `:` は許可するが、それは最後の `/` より前にのみ存在できる。
const lastSlashIndex = image.lastIndexOf('/');
const imageNamePart = lastSlashIndex === -1 ? image : image.slice(lastSlashIndex + 1);
if (imageNamePart.includes(':') || imageNamePart.includes('@')) {
return 'chromeSidecar.image must not include a tag or digest (specify them via chromeSidecar.tag instead)';
}

Copilot uses AI. Check for mistakes.
Comment on lines +216 to 229
// chromeSidecar のバリデーション(DOCKER環境の場合のみ)
const effectiveType = type ?? existing.type;
if (effectiveType === 'DOCKER') {
const chromeSidecarError = validateChromeSidecarConfig(sanitizedConfig);
if (chromeSidecarError) {
return NextResponse.json(
{ error: chromeSidecarError },
{ status: 400 }
);
}
}
// HOST環境の場合はchromeSidecarを無視(エラーにはしない)

updateData.config = sanitizedConfig;

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントでは「HOST環境の場合はchromeSidecarを無視」とありますが、現状は validateChromeSidecarConfig をスキップするだけで sanitizedConfig に残ったままDBへ保存されます。skipPermissions と同様に effectiveType !== 'DOCKER' の場合は delete sanitizedConfig.chromeSidecar して、保存自体もしない挙動に揃えてください。

Copilot uses AI. Check for mistakes.
Comment on lines +367 to +384
// 停止済みまたは存在しないコンテナ: DB更新とネットワーク削除
try {
const networkName = this.getNetworkName(session.id);
const network = docker.getDockerInstance().getNetwork(networkName);
await network.remove();
} catch {
// ネットワークも既に削除済みの場合がある
}

db.update(schema.sessions)
.set({
chrome_container_id: null,
chrome_debug_port: null,
updated_at: new Date(),
})
.where(eq(schema.sessions.id, session.id))
.run();

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanupOrphaned() のPhase 1で、ネットワーク削除が失敗しても(例: まだClaudeコンテナが接続中でremoveできない等)無条件に chrome_container_id/chrome_debug_port をNULLクリアしています。この状態になると destroySession 側がサイドカー停止/ネットワーク削除を試行できず、ネットワークがリークする可能性があります。ネットワークが削除できた/存在しないことを確認できた場合のみDBをクリアする、もしくはネットワークinspectで接続コンテナが0の場合のみクリアする等の条件付けを検討してください。

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +55
export class ChromeSidecarService {
private static instance: ChromeSidecarService;

public static getInstance(): ChromeSidecarService {
if (!ChromeSidecarService.instance) {
ChromeSidecarService.instance = new ChromeSidecarService();
}
return ChromeSidecarService.instance;
}

/** @internal テスト専用: シングルトンインスタンスをリセット */
public static resetInstance(): void {
ChromeSidecarService.instance = undefined as unknown as ChromeSidecarService;
}

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resetInstance()undefined as unknown as ChromeSidecarService と型をねじ曲げていますが、instanceChromeSidecarService | undefined にして素直に undefined を代入した方が型安全で読みやすいです(テスト専用とはいえ、将来のリファクタ時にミスを埋め込みやすくなります)。

Copilot uses AI. Check for mistakes.
Comment on lines +1374 to +1386
`if (!cfg.mcpServers) cfg.mcpServers = {};` +
`if (!cfg.mcpServers["chrome-devtools"]) {` +
`cfg.mcpServers["chrome-devtools"] = {` +
`"command": "npx",` +
`"args": ["-y", "${mcpPackage}", "--browserUrl=" + url]` +
`};` +
`} else {` +
`const args = cfg.mcpServers["chrome-devtools"].args || [];` +
`const idx = args.findIndex(a => a.startsWith("--browserUrl="));` +
`if (idx >= 0) args[idx] = "--browserUrl=" + url;` +
`else args.push("--browserUrl=" + url);` +
`cfg.mcpServers["chrome-devtools"].args = args;` +
`}` +

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.mcp.json 注入用の node -e スクリプトが cfg.mcpServers["chrome-devtools"] を常にオブジェクト、かつ args を配列として扱っています。既存 .mcp.json が想定外の形(例: chrome-devtools が文字列/args が文字列)だと例外でスクリプトが失敗し、コンテナ起動全体が失敗してセッション作成が落ちる可能性があります。chrome-devtools の型を検証してオブジェクトでなければ作り直す、args も配列以外なら配列に正規化するなど、例外が起きないようにガードしてください。

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +45
it('chromeSidecar が配列の場合: エラーメッセージを返さないこと (typeof array === object)', () => {
// 配列はtypeof === 'object'なのでオブジェクトチェックは通過するが、enabledが未定義になる
const config = { chromeSidecar: [] };
const error = validateChromeSidecarConfig(config as Record<string, unknown>);
expect(error).toBe('chromeSidecar.enabled is required and must be a boolean');
});

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

テストケース名が「エラーメッセージを返さないこと」となっていますが、実際の期待値はエラーメッセージ文字列です(配列は無効入力のはずなので期待値自体は妥当)。テストの意図が伝わるように、ケース名を「エラーメッセージを返すこと」等に修正してください。

Copilot uses AI. Check for mistakes.
windschord and others added 2 commits March 21, 2026 21:41
Copilot AI review requested due to automatic review settings March 22, 2026 02:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 36 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +224 to +235
// Chrome Sidecar設定の読み取り(ランタイムバリデーション付き)
let chromeSidecar: ChromeSidecarConfig | undefined;
const rawSidecar = (envConfig as Record<string, unknown>).chromeSidecar;
if (rawSidecar && typeof rawSidecar === 'object' && !Array.isArray(rawSidecar)) {
const s = rawSidecar as Record<string, unknown>;
if (
typeof s.enabled === 'boolean' &&
typeof s.image === 'string' &&
typeof s.tag === 'string'
) {
chromeSidecar = s.enabled ? (s as unknown as ChromeSidecarConfig) : undefined;
} else {

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTYSessionManager の chromeSidecar ランタイムバリデーションが、enabled=false の場合でも image/tag を必須としているため、{ chromeSidecar: { enabled: false } } のような(バリデータ上は許容される)設定が毎回 warn になって無視されます。enabled=true のときだけ image/tag を必須にする形に緩和し、enabled=false は追加フィールド無しでも受け入れるようにしてください。

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +36
if (typeof sidecar.image !== 'string' || !/^[-a-z0-9._/:]+$/.test(sidecar.image)) {
return 'chromeSidecar.image must be a valid Docker image name (lowercase alphanumeric, dots, slashes, colons, hyphens)';
}

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chromeSidecar.image の正規表現が : を許容しているため、ユーザーが repo:tag のようにタグ込みで入力できてしまい、最終的な Image が ${image}:${tag}repo:tag:fixed のような不正参照になる可能性があります。image にはタグ/ダイジェストを含められないようにし(例: 最後の / より後に : がある場合は拒否、@ も拒否)、registry のポート番号 registry:5000/... は許可する形に調整してください。

Suggested change
if (typeof sidecar.image !== 'string' || !/^[-a-z0-9._/:]+$/.test(sidecar.image)) {
return 'chromeSidecar.image must be a valid Docker image name (lowercase alphanumeric, dots, slashes, colons, hyphens)';
}
if (typeof sidecar.image !== 'string') {
return 'chromeSidecar.image must be a valid Docker image name (string required)';
}
const image = sidecar.image as string;
// 文字種チェック(小文字英数字、ドット、アンダースコア、スラッシュ、コロン、ハイフンのみ許可)
if (!/^[-a-z0-9._/:]+$/.test(image)) {
return 'chromeSidecar.image must be a valid Docker image name (lowercase alphanumeric, dots, slashes, hyphens, and optional registry port)';
}
// ダイジェスト(@以降)は禁止
if (image.indexOf('@') !== -1) {
return 'chromeSidecar.image must not include a digest (\'@...\')';
}
// タグは禁止。ただし registry:5000/repo のようなポート指定は許可する。
// そのため、最後の '/' より後に ':' が存在する場合のみ拒否する。
const lastSlashIndex = image.lastIndexOf('/');
const searchFrom = lastSlashIndex === -1 ? 0 : lastSlashIndex + 1;
if (image.indexOf(':', searchFrom) !== -1) {
return 'chromeSidecar.image must not include a tag; specify the tag via chromeSidecar.tag';
}

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +42
if (typeof sidecar.tag === 'string' && sidecar.tag.trim().toLowerCase() === 'latest') {
return 'chromeSidecar.tag must be a specific version (latest is not allowed for reproducibility)';

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

再現性を目的に latest を禁止している一方で、stable のような浮動タグは現在許可されています(コードコメントも「固定バージョン必須」となっています)。本当に再現性を担保したいなら、tag をバージョン形式に限定するか、少なくとも stable/beta/canary 等の既知の浮動タグも拒否するなど、仕様と実装・コメントを一致させてください。

Suggested change
if (typeof sidecar.tag === 'string' && sidecar.tag.trim().toLowerCase() === 'latest') {
return 'chromeSidecar.tag must be a specific version (latest is not allowed for reproducibility)';
if (typeof sidecar.tag === 'string') {
const tag = sidecar.tag.trim();
const lowerTag = tag.toLowerCase();
const disallowedFloatingTags = ['latest', 'stable', 'beta', 'canary', 'edge', 'dev', 'nightly', 'rc'];
if (disallowedFloatingTags.includes(lowerTag)) {
return 'chromeSidecar.tag must be a specific version (floating tags like latest/stable are not allowed for reproducibility)';
}

Copilot uses AI. Check for mistakes.
Comment on lines +272 to +279
} catch (error) {
const msg = error instanceof Error ? error.message : 'Unknown';
const isAlreadyGone =
msg.includes('404') ||
msg.includes('304') ||
msg.includes('No such container') ||
msg.includes('not running');
if (!isAlreadyGone) {

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stopSidecar の「既に消えている/停止済み」の判定が error.message の部分一致("404"/"304" など)に依存していて、Dockerode のエラー構造(statusCode 等)やメッセージ変更に弱いです。DockerAdapter.stopContainer と同様に error.statusCode を優先して判定し、message 依存をフォールバックにすることで誤判定を減らしてください。

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +17
### PUT /api/environments/:id

既存のconfig JSONバリデーションに、chromeSidecar固有のバリデーションルールを追加する。

### POST /api/environments

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

設計書が /api/environments/:id の PUT/POST を前提にしていますが、このリポジトリでは /api/environments/:id は 410 Gone の deprecated エンドポイントで、後継は /api/projects/[project_id]/environment です(src/app/api/environments/[id]/route.ts 参照)。ドキュメント内のエンドポイント表記とコード例を後継 API に更新してください。

Suggested change
### PUT /api/environments/:id
既存のconfig JSONバリデーションに、chromeSidecar固有のバリデーションルールを追加する。
### POST /api/environments
### PUT /api/projects/:project_id/environment
既存のconfig JSONバリデーションに、chromeSidecar固有のバリデーションルールを追加する。
### POST /api/projects/:project_id/environment

Copilot uses AI. Check for mistakes.
Comment on lines +270 to +274
it('tag が "stable" の場合: 正常', () => {
const config = {
chromeSidecar: { enabled: true, image: 'chromium/headless-shell', tag: 'stable' },
};
expect(validateChromeSidecarConfig(config)).toBeNull();

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このテストで tag: "stable" を「正常」として扱っていますが、実装・コメント上は再現性のため固定バージョンが前提になっており整合しません。stable を許容する仕様ならコメント/エラーメッセージを修正し、固定バージョン必須ならこのケースはエラー期待に変更してください。

Suggested change
it('tag が "stable" の場合: 正常', () => {
const config = {
chromeSidecar: { enabled: true, image: 'chromium/headless-shell', tag: 'stable' },
};
expect(validateChromeSidecarConfig(config)).toBeNull();
it('tag が "stable" の場合: エラー', () => {
const config = {
chromeSidecar: { enabled: true, image: 'chromium/headless-shell', tag: 'stable' },
};
const error = validateChromeSidecarConfig(config);
expect(error).toBe('chromeSidecar.tag must be a specific version (latest is not allowed for reproducibility)');

Copilot uses AI. Check for mistakes.
import { ChromeSidecarService } from './services/chrome-sidecar-service';

// 既存の初期化処理の後
const sidecarService = new ChromeSidecarService();

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

設計書内のコード例が new ChromeSidecarService() になっていますが、実装はシングルトンの ChromeSidecarService.getInstance() を使用しています(server.ts でも getInstance)。ドキュメントのコード例も実装に合わせて getInstance() を使う形に更新してください。

Suggested change
const sidecarService = new ChromeSidecarService();
const sidecarService = ChromeSidecarService.getInstance();

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +90
async startSidecar(
sessionId: string,
config: ChromeSidecarConfig
): Promise<SidecarStartResult>;

/**
* サイドカーChromeを停止・削除する
*
* 1. Chromeコンテナ停止 (AutoRemoveで自動削除)
* 2. セッション専用ネットワーク削除
*
* @param sessionId - セッションID
* @param containerName - Chromeコンテナ名
* @param networkName - ネットワーク名 (省略時はセッションIDから推定)
*/
async stopSidecar(
sessionId: string,
containerName: string,
networkName?: string
): Promise<void>;

/**
* Claude Codeコンテナをサイドカーネットワークに接続する
*
* Claude Codeコンテナ起動後に呼び出し、

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

設計書の ChromeSidecarService インターフェース定義で stopSidecar() の戻り値が Promise<void> になっていますが、実装は { success: boolean; error?: string } を返しています。また startSidecar も options 付きになっています。ドキュメント側のシグネチャを実装と一致させ、呼び出し側が期待すべきエラーハンドリング(graceful degradation / best-effort)も合わせて更新してください。

Copilot uses AI. Check for mistakes.
Comment thread src/types/environment.ts
Comment on lines +20 to +25
/** Chrome Sidecar設定 */
export interface ChromeSidecarConfig {
enabled: boolean;
image: string; // デフォルト: 'chromium/headless-shell'
tag: string; // 固定バージョン必須、'latest'禁止
}

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChromeSidecarConfig の型が image/tag を常に必須にしていますが、バックエンドの validateChromeSidecarConfig は enabled=false の場合に image/tag を必須としていないため、保存される config と型定義が乖離し得ます。enabled=false でも image/tag を必須にしたいならバリデーション側も必須化し、そうでないなら型を image?: string; tag?: string にしてデフォルト補完/実行時処理を合わせてください。

Copilot uses AI. Check for mistakes.
- ChromeSidecarServiceにEntrypoint(/usr/bin/chromium)を明示指定
- Cmdに--headless, --disable-dev-shm-usageフラグを追加
- デフォルトimage/tagをghcr.io/windschord/claude-work-sandbox:chrome-devtoolsに変更
- 設計書・要件定義・タスク計画のデフォルト値を更新
- テストの期待値を新デフォルトに合わせて更新

Refs #226

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/components/environments/ChromeSidecarSection.tsx (1)

39-46: role="checkbox" は冗長です(オプショナルな改善)

<input type="checkbox"> は暗黙的に checkbox ロールを持つため、role="checkbox" の明示的な指定は不要です。削除しても動作に影響はありません。

♻️ 修正案
          <input
            type="checkbox"
-           role="checkbox"
            aria-label="Chrome Sidecar を有効にする"
            checked={enabled}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/environments/ChromeSidecarSection.tsx` around lines 39 - 46,
In the ChromeSidecarSection component remove the redundant role attribute from
the checkbox input: locate the <input ... checked={enabled} ...
className="sr-only peer"> element inside ChromeSidecarSection and delete the
role="checkbox" prop since input type="checkbox" already implies the checkbox
role; leave all other props (checked, onChange, disabled, aria-label, className)
unchanged.
docs/sdd/tasks/chrome-sidecar/index.md (1)

26-43: コードブロックに言語指定を追加してください

markdownlintの警告を解消するため、依存関係図のフェンスコードブロックに言語指定(text または plaintext)を追加してください。

📝 修正案
-```
+```text
 Phase 1 (基盤 - 並列実行可能)
   TASK-001: DBスキーマ拡張
   TASK-002: 型定義拡張
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/sdd/tasks/chrome-sidecar/index.md` around lines 26 - 43, The fenced code
block in the Chrome Sidecar task doc lacks a language tag causing markdownlint
warnings; update the triple-backtick fence that wraps the dependency diagram in
the file to include a language identifier such as text or plaintext (e.g.,
change ``` to ```text) so the block is recognized as plain text by the linter;
ensure you update the opening fence only (the closing fence remains ```) and
keep the block content unchanged.
docs/sdd/design/chrome-sidecar/index.md (1)

7-25: コードブロックに言語指定を追加してください

markdownlintの警告を解消するため、フェンスコードブロックに言語指定を追加することを推奨します。ASCII図の場合は text または plaintext を使用できます。

📝 修正案
-```
+```text
 Session起動フロー (サイドカー有効時):
 
 +-------------------+         +------------------+         +-------------------+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/sdd/design/chrome-sidecar/index.md` around lines 7 - 25, The fenced code
block in docs/sdd/design/chrome-sidecar/index.md has no language tag which
triggers markdownlint; update the opening fence for the ASCII diagram in the
file to include a language specifier (e.g., text or plaintext) so the block
reads as a labeled fenced code block; ensure only the opening backticks are
changed (leave the diagram content and closing backticks intact) so tools
recognize it as plain text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/sdd/design/chrome-sidecar/index.md`:
- Around line 7-25: The fenced code block in
docs/sdd/design/chrome-sidecar/index.md has no language tag which triggers
markdownlint; update the opening fence for the ASCII diagram in the file to
include a language specifier (e.g., text or plaintext) so the block reads as a
labeled fenced code block; ensure only the opening backticks are changed (leave
the diagram content and closing backticks intact) so tools recognize it as plain
text.

In `@docs/sdd/tasks/chrome-sidecar/index.md`:
- Around line 26-43: The fenced code block in the Chrome Sidecar task doc lacks
a language tag causing markdownlint warnings; update the triple-backtick fence
that wraps the dependency diagram in the file to include a language identifier
such as text or plaintext (e.g., change ``` to ```text) so the block is
recognized as plain text by the linter; ensure you update the opening fence only
(the closing fence remains ```) and keep the block content unchanged.

In `@src/components/environments/ChromeSidecarSection.tsx`:
- Around line 39-46: In the ChromeSidecarSection component remove the redundant
role attribute from the checkbox input: locate the <input ... checked={enabled}
... className="sr-only peer"> element inside ChromeSidecarSection and delete the
role="checkbox" prop since input type="checkbox" already implies the checkbox
role; leave all other props (checked, onChange, disabled, aria-label, className)
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 729019c2-b493-4929-a64b-2817f91c9563

📥 Commits

Reviewing files that changed from the base of the PR and between 04de50f and b0ce416.

📒 Files selected for processing (27)
  • docs/sdd/design/chrome-sidecar/api/environment-api.md
  • docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md
  • docs/sdd/design/chrome-sidecar/components/db-schema.md
  • docs/sdd/design/chrome-sidecar/components/environment-ui.md
  • docs/sdd/design/chrome-sidecar/index.md
  • docs/sdd/requirements/chrome-sidecar/index.md
  • docs/sdd/requirements/chrome-sidecar/stories/US-003.md
  • docs/sdd/tasks/chrome-sidecar/index.md
  • docs/sdd/tasks/chrome-sidecar/phase-1/TASK-001.md
  • docs/sdd/tasks/chrome-sidecar/phase-1/TASK-002.md
  • docs/sdd/tasks/chrome-sidecar/phase-2/TASK-003.md
  • docs/sdd/tasks/chrome-sidecar/phase-2/TASK-004.md
  • docs/sdd/tasks/chrome-sidecar/phase-3/TASK-005.md
  • docs/sdd/tasks/chrome-sidecar/phase-3/TASK-006.md
  • docs/sdd/tasks/chrome-sidecar/phase-4/TASK-007.md
  • docs/sdd/tasks/chrome-sidecar/phase-4/TASK-008.md
  • docs/sdd/tasks/chrome-sidecar/phase-4/TASK-009.md
  • docs/sdd/tasks/index.md
  • src/app/api/environments/__tests__/chrome-sidecar-validation.test.ts
  • src/components/environments/ChromeSidecarSection.tsx
  • src/components/environments/__tests__/chrome-sidecar-form.test.tsx
  • src/components/projects/ProjectEnvironmentSection.tsx
  • src/services/__tests__/chrome-sidecar-service.test.ts
  • src/services/adapters/__tests__/docker-adapter-chrome-sidecar.test.ts
  • src/services/chrome-sidecar-service.ts
  • src/types/__tests__/chrome-sidecar-config.test.ts
  • src/types/environment.ts
✅ Files skipped from review due to trivial changes (18)
  • docs/sdd/tasks/index.md
  • docs/sdd/requirements/chrome-sidecar/index.md
  • docs/sdd/requirements/chrome-sidecar/stories/US-003.md
  • src/types/environment.ts
  • docs/sdd/tasks/chrome-sidecar/phase-4/TASK-008.md
  • docs/sdd/tasks/chrome-sidecar/phase-4/TASK-007.md
  • src/types/tests/chrome-sidecar-config.test.ts
  • docs/sdd/tasks/chrome-sidecar/phase-4/TASK-009.md
  • docs/sdd/tasks/chrome-sidecar/phase-2/TASK-003.md
  • docs/sdd/tasks/chrome-sidecar/phase-1/TASK-001.md
  • docs/sdd/tasks/chrome-sidecar/phase-2/TASK-004.md
  • docs/sdd/design/chrome-sidecar/components/db-schema.md
  • docs/sdd/tasks/chrome-sidecar/phase-3/TASK-005.md
  • src/app/api/environments/tests/chrome-sidecar-validation.test.ts
  • docs/sdd/design/chrome-sidecar/api/environment-api.md
  • docs/sdd/design/chrome-sidecar/components/environment-ui.md
  • docs/sdd/design/chrome-sidecar/components/chrome-sidecar-service.md
  • src/services/tests/chrome-sidecar-service.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/chrome-sidecar-service.ts

Copilot AI review requested due to automatic review settings March 23, 2026 22:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 49 out of 49 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +37 to +43
if (typeof sidecar.tag !== 'string' || sidecar.tag.trim() === '') {
return 'chromeSidecar.tag is required';
}

if (typeof sidecar.tag === 'string' && sidecar.tag.trim().toLowerCase() === 'latest') {
return 'chromeSidecar.tag must be a specific version (latest is not allowed for reproducibility)';
}

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chromeSidecar.tag が空/latest 以外は無条件に受け入れており、スペース等のDockerタグとして不正な値でも API を通って実行時エラーになります。Dockerタグ仕様(例: [A-Za-z0-9_][A-Za-z0-9_.-]{0,127})に沿った形式チェックを追加して、実行時失敗を事前に400で返すようにしてください。

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +91
| ネットワーク作成失敗 | セッション作成をエラーとして中止、作成途中のリソースをロールバック | NFR-SEC-002 |
| Chromeコンテナ起動失敗 | ネットワーク削除後、サイドカーなしでClaude Code起動 | REQ-001-004 |
| CDPヘルスチェックタイムアウト | Chromeコンテナ停止、ネットワーク削除後、サイドカーなしで起動 | NFR-RES-002 |
| ポートマッピング失敗 | サイドカーChromeをホストポートなしで起動、chrome_debug_port=NULL | REQ-004-001 |
| .mcp.json注入失敗 | 警告ログ出力、セッション起動は続行 | graceful degradation |

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここでは「ネットワーク作成失敗はセッション作成をエラーとして中止」とありますが、実装(DockerAdapter)は startSidecar() 失敗時にサイドカー無しで続行する挙動になっています。設計書の方針と実装のどちらかに合わせて、この表の記述を更新してください。

Copilot uses AI. Check for mistakes.
Comment on lines +33 to 47
interface ChromeSidecarUIConfig {
enabled: boolean;
image: string;
tag: string;
}

interface EnvironmentConfig {
imageName?: string;
imageTag?: string;
skipPermissions?: boolean;
portMappings?: PortMapping[];
volumeMounts?: VolumeMount[];
claude_defaults_override?: ClaudeDefaultsOverride;
chromeSidecar?: ChromeSidecarUIConfig;
}

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChromeSidecarUIConfigChromeSidecarConfig と実質同じ定義になっており、将来どちらかだけ更新されると型が乖離します。@/types/environment から ChromeSidecarConfig を import して EnvironmentConfig.chromeSidecar?: ChromeSidecarConfig に統一するなど、型定義の重複を避けてください。

Copilot uses AI. Check for mistakes.
Comment on lines 88 to +94
/**
* 全マイグレーションSQLを順番に実行してテーブルを作成する(__drizzle_migrationsなし)
* Drizzle migrate()を通さず、SQLを直接実行してテーブルだけを作る
*/
function applyAllMigrationsSqlDirectly(sqlite: InstanceType<typeof Database>): void {
/**
* 初期マイグレーションSQL(CREATE TABLE)のみ直接実行してテーブルを作成する(__drizzle_migrationsなし)
*

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applyInitialMigrationSqlDirectly の説明コメントの直前に、旧 applyAllMigrationsSqlDirectly 向けのJSDoc(「全マイグレーションSQLを順番に実行…」)が残っており、内容が矛盾しています。不要な旧コメントを削除して、新しい関数の説明だけが残るよう整理してください。

Copilot uses AI. Check for mistakes.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Chrome Sidecar MCP環境の実装

2 participants